<template>
  <el-drawer
    ref="drawerBox"
    :before-close="handleClose"
    v-bind="$attrs"
    :close-on-press-escape="false"
    :size="600"
    title="评论区"
    v-on="$listeners"
  >
    <slot />
    <div v-pull-refresh="pullRefresh" :style="{height: drawerContentHeight}" class="drawer-content">
      <template v-if="comments.length">
        <div id="pullRefreshBox" class="drawer-content-box">
          <div class="drawer-loading">
            <img v-if="dropLoading" :src="dropLoadingImg" alt="">
          </div>
          <div v-if="showNoMoreTip" style="color: #999; text-align: center; margin-top: 5px">没有更多了</div>
          <template v-for="(comment, index) in comments">
            <div :key="index" class="comment-box">
              <div class="user-info">
                <img :src="comment.avatar || avatarImg" alt="">
                <div class="user-info-box">
                  <div>
                    <div class="user-name">{{ comment.userName }}</div>
                    <div style="margin-top: 6px">{{ comment.post }}</div>
                  </div>
                  <div class="flex-center" style="align-self: flex-end">
                    <div class="reply-box" @click="handleReply(comment)">回复<img :src="replyImg" alt="回复"></div>
                    {{ comment.sendTime | standardFormat('YYYY/MM/DD HH:mm') }}
                  </div>
                </div>
              </div>
              <div class="comment-content ueditor-content">
                <div :class="comment.userId === userInfo.id && 'highlight-box'" class="content" v-html="comment.content" />
              </div>
            </div>
          </template>
          <div v-show="showNewCommentsTips" class="new-comment-tips" @click="viewNewComments">
            <img :src="fingerDownImg" alt="">查看新评论
          </div>
        </div>
      </template>
      <template v-else>
        <div :style="{ transform: editorShow ? 'translateY(0)' : 'translateY(-10vh)' }" class="comment-empty">
          <img v-if="emptyImgShow" :src="commentEmptyImg">
          <div class="comment-empty-tip">暂无评论</div>
        </div>
      </template>
    </div>
    <div v-if="editorShow">
      <div v-if="replyContent.content" class="reply-content">
        <div class="reply-content-box">
          <div style="padding-bottom: 6px">{{ replyContent.userName }}：</div>
          <div class="reply-text-box ueditor-content" v-html="replyContent.content" />
        </div>
        <i class="el-icon-circle-close reply-cancel-icon" @click="replyContentClear" />
        <div class="left-divider" />
      </div>
      <RichEditor
        v-model="commentVal"
        :editor-height="300"
        :padding-left="0"
        :default-mention="replyUser"
        is-show-mention
        class="ueditorContainer"
      />
      <i class="el-icon-arrow-down hide-editor" title="收起" @click="editorShow = false" />
      <div class="footer-btn-group">
        <el-button :disabled="isSending || !sendBtnActive" type="primary" size="small" @click="handleSend">发送</el-button>
        <el-button type="primary" plain size="small" @click="handleCancel">返回</el-button>
      </div>
    </div>
    <div v-else class="add-comment-container" @click="editorShow = true">
      <div>
        <i class="el-icon-chat-dot-square" style="font-size: 16px;" />
        <span class="add-comment-title">添加评论</span>
      </div>
      <i class="el-icon-arrow-up show-editor-icon" />
    </div>
  </el-drawer>
</template>

<script>
import RichEditor from '@/components/Editor/RichEditor';
import avatarImg from '@/assets/user-avatar.png';
import dropLoadingImg from '@/assets/legal/dropLoading.gif';
import commentEmptyImg from '@/assets/legal/comment-empty.png';
import fingerDownImg from '@/assets/legal/fingerDown.png';
import replyImg from '@/assets/legal/reply.png';
import { standardFormat } from '@/filters/date';
import pullRefresh from '@/directive/pullRefresh';
import { mapState } from 'vuex';
export default {
  name: 'CommentDialog',
  components: {
    RichEditor
  },
  filters: {
    standardFormat
  },
  directives: {
    pullRefresh
  },
  props: {
    getCommentsApi: {
      type: Function,
      default: () => ({})
    },
    addCommentApi: {
      type: Function,
      default: () => ({})
    },
    commentModule: {
      type: String,
      default: ''
    },
    commentsId: {
      type: [String, Number],
      default: ''
    },
    isCreator: { // 是否为创建人
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      avatarImg,
      dropLoadingImg,
      commentEmptyImg,
      fingerDownImg,
      replyImg,
      commentVal: '',
      editorShow: false,
      webSocket: null,
      pageSize: 20,
      page: 1,
      dropLoading: false,
      noMore: false,
      comments: [], // 评论列表,
      nextPageComments: [],
      emptyImgShow: true,
      showNewCommentsTips: false,
      drawerContentHeight: '100vh',
      drawerContentContainerHeight: 'calc(100vh - 48px)',
      replyUser: null, // 被回复人员
      replyContent: '',
      sendTimer: null,
      isSending: false
    };
  },
  computed: {
    ...mapState({
      'userInfo': state => state.user.userInfo
    }),
    showNoMoreTip() {
      return this.noMore && this.comments.length > 10;
    },
    sendBtnActive() {
      if (!this.commentVal) return false;
      const commentContent = this.commentVal.replace(/<\/?(?!(img|table|video)).+?>/g, '');
      return commentContent ? commentContent.replace(/&nbsp;|\s/g, '') : false;
    }
  },
  watch: {
    editorShow: {
      handler(val) {
        if (val) {
          const timer = setTimeout(() => {
            const editorDom = document.querySelector('.ueditorContainer');
            this.drawerContentHeight = `calc(${this.drawerContentContainerHeight} - ${editorDom.offsetHeight}px - ${this.replyContent ? 166 : 66}px)`;
            this.scrollToBottom();
            this.$nextTick(() => {
              !this.comments.length && this.showEmptyImg();
            });
            clearTimeout(timer);
          }, 200);
        } else {
          this.drawerContentHeight = `calc(${this.drawerContentContainerHeight} - 66px)`;
          this.$nextTick(() => {
            !this.comments.length && this.showEmptyImg();
          });
        }
      },
      immediate: true
    },
    commentVal(val) {
      this.commentVal = val.replace(/(^\s*)|(\s*$)/g, '');
    },
    drawerContentContainerHeight(val) {
      this.drawerContentHeight = `calc(${val} - 66px)`;
    }
  },
  methods: {
    initDrawerContentHeight() {
      this.$nextTick(() => {
        const headerDom = document.querySelector('.el-drawer__body');
        if (headerDom && headerDom.clientHeight) this.drawerContentContainerHeight = headerDom.clientHeight + 'px';
        this.handleScrollEvents();
      });
    },
    handleScrollEvents() {
      const pullRefreshDom = document.querySelector('#pullRefreshBox');
      pullRefreshDom && pullRefreshDom.addEventListener('scroll', () => {
        this.handleNewComment(true);
      });
    },
    initWebSocket() {
      try {
        const baseUrl = process.env.WS_CONNECT_IP;
        const url = `${baseUrl}/websocket/${this.commentModule}/${this.commentsId}`;
        const ws = new WebSocket(url);
        ws.addEventListener('close', this.handleWsClose.bind(this), false);
        ws.addEventListener('error', this.handleWsError.bind(this), false);
        ws.addEventListener('message', this.handleWsMessage.bind(this), false);
        this.webSocket = ws;
      } catch (err) {
        this.$nextTick(() => {
          this.$message.warning('无法连接到评论区');
        });
      }
    },
    // 获取评论列表数据
    async queryComments(isLoadMore = false) {
      try {
        if (this.dropLoading || this.noMore) return;
        isLoadMore && (this.dropLoading = true);
        const params = Object.create(null);
        params.id = this.commentsId;
        params.page = isLoadMore ? ++this.page : this.page;
        params.perPage = this.pageSize;
        const result = await this.getCommentsApi(params);
        this.nextPageComments = this.formatComments(result);
        this.nextPageComments.length < this.pageSize && (this.noMore = true);
        isLoadMore ? this.comments.unshift(...this.nextPageComments) : this.comments = this.nextPageComments;
        this.dropLoading = false;
      } catch (e) {
        this.dropLoading = false;
      }
    },
    formatComments(commentData) {
      if (!commentData || !Array.isArray(commentData.data) || !commentData.data.length) return [];
      const result = commentData.data.reverse();
      return result.map(comment => this.setNotifyToDom(comment));
    },
    setNotifyToDom(comment) {
      const { content, notifyUsers, isNewRichText } = comment;
      if (notifyUsers && notifyUsers.length && !isNewRichText) {
        let notifyUserDom = '';
        notifyUsers.forEach(user => {
          notifyUserDom += `<span style="white-space: nowrap; color: #406EFF; display: inline-block; margin-right: 0.4em"> @${user.userName}</span>`;
        });
        if (content.endsWith('</p>')) {
          comment.content = this.appendNotifyDom(content, notifyUserDom, 4);
        } else if (content.endsWith('</div>')) {
          comment.content = this.appendNotifyDom(content, notifyUserDom, 6);
        } else {
          comment.content = this.appendNotifyDom(content, notifyUserDom, 0);
        }
      }
      return comment;
    },
    appendNotifyDom(content, notifyUserDom, num) {
      let result = '';
      let contentLen = content.length;
      // 去除@符号前的换行符
      const linefeed = content.substring(contentLen - (num + 5), contentLen - num);
      if (linefeed === '<br/>') {
        content = content.slice(0, contentLen - (num + 5)) + content.slice(contentLen - num);
        contentLen -= 5;
      }
      switch (num) {
        case 4:
        case 6:
          result = content.slice(0, contentLen - num) + notifyUserDom + content.slice(contentLen - num);
          break;
        default:
          result = content + notifyUserDom;
          break;
      }
      return result;
    },
    // 下拉加载
    async pullRefresh() {
      if (this.noMore) return;
      await this.queryComments(true);
      // 让滚动条回到获取下个分页前的位置
      const lastCommentDom = document.querySelectorAll('.comment-box')[this.nextPageComments.length];
      lastCommentDom && lastCommentDom.scrollIntoView(true);
    },
    // 默认滚动到底部，新评论也在底部显示
    scrollToBottom(useAnimation = false) {
      this.$nextTick(() => {
        const commentsNode = document.querySelectorAll('.comment-box');
        if (commentsNode && commentsNode.length) {
          const lastDom = commentsNode[this.comments.length - 1];
          if (useAnimation) {
            lastDom && lastDom.scrollIntoView(false);
          } else {
            lastDom && lastDom.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
          }
          this.showNewCommentsTips = false;
        }
      });
    },
    // 是否显示空评论图片
    showEmptyImg() {
      const drawerContentDom = document.querySelector('.drawer-content');
      this.emptyImgShow = !(drawerContentDom && drawerContentDom.clientHeight < 200);
    },
    formatCommentVal() {
      // 展示时给@元素块增加样式
      return this.commentVal.replace(/data-w-e-type="mention"/g, `"data-w-e-type="mention" style="color: #406EFF"`);
    },
    // 获取所有评论的@人员id
    getNotifyUserIds() {
      let result = this.commentVal.match(/(%3A)(\d*)(?=%7D)/g);
      if (Array.isArray(result) && result.length) {
        result = result.map(i => {
          return i.replace('%3A', '');
        });
        const ids = result.filter(id => !isNaN(Number(id)));
        return [...new Set(ids)].join();
      }
      return '';
    },
    // 发送评论
    async handleSend() {
      try {
        const sendData = Object.create(null);
        sendData.id = this.commentsId;
        sendData.content = this.formatCommentVal();
        sendData.notifyUserIds = this.getNotifyUserIds();
        sendData.isNewRichText = true; // 新版富文本可在内部使用@功能，无需在末尾追加人员
        const replyContent = this.setReplyContent();
        replyContent && (sendData.content = replyContent.content + sendData.content);
        this.resetData();
        replyContent && this.resetDrawerContentHeight(false);
        this.setTimer();
        await this.addCommentApi(sendData);
        this.scrollToBottom(true);
      } catch (e) {
        this.scrollToBottom(true);
      }
    },
    setTimer() {
      this.isSending = true;
      this.sendTimer && clearTimeout(this.sendTimer);
      this.sendTimer = setTimeout(() => {
        if (this.isSending) {
          this.isSending = false;
          this.$message.warning('评论功能开小差啦，请刷新后重试');
        }
        clearTimeout(this.sendTimer);
        this.sendTimer = null;
      }, 5000);
    },
    // 设置回复的评论样式
    setReplyContent() {
      if (!this.replyContent) return;
      const replyContent = JSON.parse(JSON.stringify(this.replyContent));
      replyContent.content = `<div id="replyContent" style="font-size: 12px; padding: 0 0 12px 12px; border-bottom: 1px solid #ddd; margin-bottom: 10px">
                                <div style="color: #999 !important;">${replyContent.userName}：${replyContent.content}</div>
                              </div>`;
      return replyContent;
    },
    // 取消回复
    handleCancel() {
      const cancelCallBack = this.$refs['drawerBox'].closeDrawer;
      this.cancelConfirm(cancelCallBack);
    },
    handleClose(done) {
      this.cancelConfirm(done);
    },
    resetDrawer() {
      this.noMore = false;
      this.editorShow = false;
      this.page = 1;
      this.comments = [];
      const hasReplyContent = !!this.replyContent;
      this.resetData();
      hasReplyContent && (this.drawerContentHeight = `calc(${this.drawerContentHeight} - 100px)`);
    },
    resetData() {
      this.commentVal = '';
      this.replyUser = null;
      this.replyContent = '';
    },
    cancelConfirm(callback, title = '评论还未发送，确认关闭吗？') {
      if (this.sendBtnActive) {
        this.$confirm(title).then(_ => {
          this.closeWebSocket();
          this.resetDrawer();
          callback();
        }).catch(_ => null);
      } else {
        this.closeWebSocket();
        this.resetDrawer();
        callback();
      }
    },
    handleWsError() {
      this.$nextTick(() => {
        this.$message.warning('无法连接到评论区');
      });
    },
    handleWsClose() {
      this.$nextTick(() => {
        this.$refs['drawerBox'] && this.$refs['drawerBox'].visible && this.$message.warning('评论功能开小差啦，请刷新后重试');
      });
    },
    handleWsMessage(comment) {
      if (!comment || typeof comment.data !== 'string') return;
      const parseComment = comment.data && JSON.parse(comment.data);
      if (parseComment.userId === this.userInfo.id) {
        this.sendTimer && (this.isSending = false);
      }
      this.comments.push(parseComment);
      this.$emit('commentCountChange');
      this.$nextTick(() => {
        this.comments.length === 1 && this.handleScrollEvents();
        this.handleNewComment();
      });
    },
    // 判断是滚动到底部还是显示新评论按钮
    handleNewComment(scrollBottom = false) {
      if (this.showNewCommentsTips && !scrollBottom) return;
      const scrollDom = document.querySelector('#pullRefreshBox');
      const drawerContainer = document.querySelector('.drawer-content');
      const scrollHeight = scrollDom && scrollDom.scrollHeight;
      const scrollTopHeight = scrollDom && scrollDom.scrollTop;
      const drawerContainerHeight = drawerContainer.clientHeight;
      if ((scrollHeight - scrollTopHeight) > (drawerContainerHeight + 200)) {
        !scrollBottom && (this.showNewCommentsTips = true);
      } else {
        this.showNewCommentsTips = false;
        !scrollBottom && this.$nextTick(() => {
          this.scrollToBottom();
        });
      }
    },
    viewNewComments() {
      this.showNewCommentsTips = false;
      this.scrollToBottom();
    },
    closeWebSocket() {
      this.webSocket && this.webSocket.close();
    },
    // 回复
    handleReply(comment) {
      this.editorShow = true;
      this.resetDrawerContentHeight();
      this.replyContent = this.getReplyContent(comment);
      // 设置被回复人消息，回复时默认显示于富文本
      this.replyUser = { id: comment.userId, userName: comment.userName };
    },
    // 禁止盖楼，获取所要回复的评论
    getReplyContent(comment) {
      const commentCopy = JSON.parse(JSON.stringify(comment));
      const tempNode = document.createElement('div');
      tempNode.innerHTML = commentCopy.content;
      const oldReplyDom = tempNode.querySelector('#replyContent');
      if (!oldReplyDom) return comment;
      oldReplyDom.parentNode.removeChild(oldReplyDom);
      commentCopy.content = this.nodeToString(tempNode);
      return commentCopy;
    },
    // dom转化成字符串
    nodeToString(node) {
      let tmpNode = document.createElement('div');
      tmpNode.appendChild(node.cloneNode(true));
      const str = tmpNode.innerHTML;
      tmpNode = node = null;
      return str;
    },
    // 取消回复
    replyContentClear() {
      this.replyContent = '';
      this.resetDrawerContentHeight(false);
    },
    resetDrawerContentHeight(isAddition = true) {
      if (this.replyContent) return;
      this.drawerContentHeight = `calc(${this.drawerContentHeight} ${isAddition ? '-' : '+'} 100px)`;
    },
    // 评论图片查看
    onClickComment(e) {
      e.stopPropagation();
      if (e.target.localName === 'img' && e.target.nodeName === 'IMG') {
        window.open(e.target.currentSrc, '_target');
      }
    }
  }
};
</script>

<style scoped lang="scss">
::v-deep {
  .el-drawer {
    max-width: 100vw;
    overflow: hidden;
  }
  .el-drawer__header {
    height: 48px;
    padding: 0 24px;
    margin: 0;
    box-shadow: 2px 4px 5px 0 rgba(221,225,234,0.3);
    > span {
      font-size: 14px;
      color: #333;
    }
  }
  .edui-default td {
    border-top: none !important;
  }
  .w-e-text-container {
    font-size: 14px;
  }
}
.ueditor-content {
  color: #666;
  font-size: 14px;
  text-align: justify;
  &::v-deep {
    img, video {
      border-radius: 8px !important;
      max-width: 300px;
      height: auto;
    }
    a {
      background-color: transparent !important;
      color: #406EFF !important;
      word-break: break-word;
    }
    ol, ul, p {
      margin: 0;
    }
    p {
      line-height: 1.5;
      text-align: left;
      overflow-wrap: break-word;
      word-break: break-word;
    }
    blockquote {
      background-color: #f5f2f0;
      border-left: 8px solid #b4d5ff;
      display: block;
      font-size: 100%;
      line-height: 1.5;
      margin: 10px 0 16px;
      padding: 10px;
    }
    table {
      border-collapse: collapse;
      padding: 0;
      border: 1px solid #ccc;
      border-radius: 3px;
      margin-bottom: 6px;
      tbody > tr:first-child {
        background-color: #eee;
      }
      th, td {
        border-right: 1px solid #ccc;
        border-top: 1px solid #ccc;
      }
      td:last-child, th:last-child {
        border-right: none;
      }
    }
  }
}
.drawer-content {
  height: calc(100vh - 48px);
  overflow-y: hidden;
  position: relative;
  .drawer-loading {
    position: absolute;
    top: 5px;
    width: 100%;
    display: flex;
    justify-content: center;
  }
  &-box {
    overflow-y: auto;
    overflow-x: hidden;
    height: 100%;
    .new-comment-tips {
      position: absolute;
      bottom: 20px;
      right: 0;
      width: 115px;
      height: 35px;
      color: #406EFF;
      line-height: 35px;
      font-size: 14px;
      text-align: center;
      background-color: #fff;
      box-shadow: rgba(221,225,234, 1) 0 0 6px;
      border-top-left-radius: 999px;
      border-bottom-left-radius: 999px;
      cursor: pointer;
      > img {
        vertical-align: middle;
        margin-right: 3px;
      }
    }
  }
  .comment-box {
    padding: 12px 24px 0;
    .user-info {
      height: 40px;
      display: flex;
      align-items: center;
      &-box {
        height: 100%;
        flex: 1;
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-left: 8px;
        color: #999;
        .user-name {
          font-size: 14px;
          font-weight: bold;
          color: #333;
        }
      }
      > img {
        width: 40px;
        height: 40px;
        border-radius: 50%;
      }
      .reply-box {
        cursor: pointer;
        display: flex;
        align-items: center;
        font-size: 12px;
        color: #406EFF;
        margin-right: 32px;
        > img {
          width: 16px;
          margin-left: 2px;
        }
      }
    }
    .comment-content {
      position: relative;
      .content {
        padding: 12px;
        border-radius: 8px;
        background-color: #F9F9F9;
        margin-top: 8px;
      }
    }
    .highlight-box {
      background-color: #EFF3FB !important;
    }
  }
  .comment-empty {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    &-tip {
      color: #999;
    }
    > img {
      margin-bottom: 8px;
    }
  }
}
.ueditorContainer {
  position: absolute;
  bottom: 56px;
  width: 100%;
}
.reply-content {
  position: absolute;
  bottom: 392px;
  width: 100%;
  max-height: 100px;
  padding: 12px 24px;
  background-color: #fff;
  box-shadow: 0 3px 8px #ccc;
  color: #666;
  .reply-content-box {
    padding: 0 12px;
    position: relative;
    color: #999;
  }
  .reply-text-box {
    font-size: 12px;
    overflow-y: auto;
    height: 70px;
    padding-bottom: 12px;
    color: #999;
  }
  .left-divider {
    width: 2px;
    height: 76px;
    position: absolute;
    left: 24px;
    top: 12px;
    background-color: #eee;
  }
  .reply-cancel-icon {
    font-size: 20px;
    position: absolute;
    top: 12px;
    right: 10px;
    cursor: pointer;
  }
  .reply-cancel-icon:hover {
    color: #406EFF;
  }
}
.hide-editor {
  width: 30px;
  line-height: 31px;
  position: absolute;
  bottom: 362px;
  right: 0;
  z-index: 9999;
  color: #406EFF;
  font-size: 20px;
  cursor: pointer;
}
.footer-btn-group {
  position: absolute;
  bottom: 0;
  height: 56px;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 20px;
  button {
    border: none;
  }
}
.add-comment-container {
  height: 56px;
  padding: 0 24px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: absolute;
  bottom: 0;
  width: 100%;
  border-top: 1px solid #ccc;
  cursor: pointer;
  background-color: #fff;
  .add-comment-title {
    margin-left: 3px;
    font-size: 14px;
    color: #666;
  }
  .show-editor-icon {
    display: block;
    font-size: 20px;
    color: #406EFF;
  }
}
</style>
